﻿using System;
using System.Collections.Generic;
using System.Linq;

namespace PirateMap.Models.cls
{
  public class Map
  {
    #region Variables & ctors

    public Map()
    {
      _maps = new Dictionary<string, Cell[,]>();
      _playerLocations = new Dictionary<string, MapInfo>();
      _isWin = new Dictionary<string, bool>();

      GenerateSample();
    }
    public Map(IEnumerable<int> mapSizes)
    {
      _maps = new Dictionary<string, Cell[,]>();
      _playerLocations = new Dictionary<string, MapInfo>();
      _isWin = new Dictionary<string, bool>();

      GenerateMap(mapSizes);
    }

    private Random _random = new Random(DateTime.Now.Millisecond);
    private Cell _moon;
    private Cell _earth;

    private Dictionary<string, Cell[,]> _maps;
    private Dictionary<string, MapInfo> _playerLocations;
    private Dictionary<string, bool> _isWin;

    #endregion

    #region Public

    public void AddNewPlayer(string playerId)
    {
      if (_playerLocations.ContainsKey(playerId) || _isWin.ContainsKey(playerId))
        throw new PlayerExistException();

      _playerLocations.Add(playerId, new MapInfo() { HasTreasure = false, Position = _earth });
      _isWin.Add(playerId, false);
    }
    public string Move(string userId, DirectionTag direction)
    {
      if (!_playerLocations.ContainsKey(userId))
        throw new PlayerNotFoundException();

      string result;

      switch (direction)
      {
        case DirectionTag.Up:
          result = MoveUp(userId).ToString();
          break;
        case DirectionTag.Down:
          result = MoveDown(userId).ToString();
          break;
        case DirectionTag.Left:
          result = MoveLeft(userId).ToString();
          break;
        case DirectionTag.Right:
          result = MoveRight(userId).ToString();
          break;
        default:
          throw new InvalidMoveException();
      }

      if (_isWin[userId]) 
        result = "WIN";

      return result;
    }
    public bool IsWin(string userId)
    {
      if (!_isWin.ContainsKey(userId))
        throw new PlayerNotFoundException();

      return _isWin[userId];
    }

    #endregion

    #region Private

    private void GenerateMap(IEnumerable<int> mapSizes)
    {
      if (mapSizes == null || !mapSizes.Any())
        throw new ArgumentException("There is no map sizes.");

      if (mapSizes.Count() == 1)
      {

        return;
      }

      foreach (var size in mapSizes)
      {

      }
    }
    private void GenerateSample()
    {
      var mapId = Guid.NewGuid().ToString("N");

      _earth = new Cell(0, 0, CellTag.Earth, mapId);
      _moon = new Cell(3, 3, CellTag.Moon, mapId);
      var treasure = new Cell(3, 0, CellTag.Treasure, mapId);

      var alien1 = new Cell(0, 1, CellTag.Alien, mapId);
      var alien2 = new Cell(3, 1, CellTag.Alien, mapId);
      var alien3 = new Cell(1, 2, CellTag.Alien, mapId);
      var alien4 = new Cell(3, 2, CellTag.Alien, mapId);
      var alien5 = new Cell(1, 3, CellTag.Alien, mapId);

      var bh1 = new Cell(2, 2, CellTag.BlackHole, mapId);
      var bh2 = new Cell(0, 2, CellTag.BlackHole, mapId);
      var bh3 = new Cell(0, 3, CellTag.BlackHole, mapId);
      var bh4 = new Cell(2, 3, CellTag.BlackHole, mapId);

      bh1.SetNextBlackHole(bh2);
      bh2.SetNextBlackHole(bh3);
      bh3.SetNextBlackHole(bh4);
      bh4.SetNextBlackHole(bh1);

      var m1 = new Cell(1, 0, CellTag.MeteorBelt, mapId, 2, DirectionTag.Right);
      var m2 = new Cell(2, 0, CellTag.MeteorBelt, mapId, 2, DirectionTag.Down);
      var m3 = new Cell(2, 1, CellTag.MeteorBelt, mapId, 2, DirectionTag.Left);
      var m4 = new Cell(1, 1, CellTag.MeteorBelt, mapId, 2, DirectionTag.Up);

      m1.SetNextMeteorBelt(m2);
      m2.SetNextMeteorBelt(m3);
      m3.SetNextMeteorBelt(m4);
      m4.SetNextMeteorBelt(m1);

      var first = new[,]
      {
        {_earth, m1, m2, treasure},
        {alien1, m4, m3, alien2},
        {bh2, alien3,  bh1, alien4},
        {bh3, alien5, bh4, _moon}
      };

      _maps.Add(mapId, first);
    }

    private Cell MoveUp(string userId)
    {
      var playerLocation = _playerLocations[userId];
      var currMap = _maps[playerLocation.Position.MapId];

      var newX = playerLocation.Position.X;
      var newY = playerLocation.Position.Y - 1;

      if (newY < 0)
        return new Cell();

      var next = currMap[newY, newX];

      return MoveNext(userId, next);
    }
    private Cell MoveDown(string userId)
    {
      var playerLocation = _playerLocations[userId];
      var currMap = _maps[playerLocation.Position.MapId];

      var newX = playerLocation.Position.X;
      var newY = playerLocation.Position.Y + 1;

      if (newY >= currMap.GetLength(0))
        return new Cell();

      var next = currMap[newY, newX];

      return MoveNext(userId, next);
    }
    private Cell MoveLeft(string userId)
    {
      var playerLocation = _playerLocations[userId];
      var currMap = _maps[playerLocation.Position.MapId];

      var newX = playerLocation.Position.X - 1;
      var newY = playerLocation.Position.Y;

      if (newX < 0)
        return new Cell();

      var next = currMap[newY, newX];

      return MoveNext(userId, next);
    }
    private Cell MoveRight(string userId)
    {
      var playerLocation = _playerLocations[userId];
      var currMap = _maps[playerLocation.Position.MapId];

      var newX = playerLocation.Position.X + 1;
      var newY = playerLocation.Position.Y;

      if (newX >= currMap.GetLength(1))
        return new Cell();

      var next = currMap[newY, newX];

      return MoveNext(userId, next);
    }

    private Cell MoveNext(string userId, Cell next)
    {
      var playerLocation = _playerLocations[userId];

      switch (next.CellType)
      {
        case CellTag.Treasure:
          _playerLocations[userId] = new MapInfo() { HasTreasure = true, Position = next };
          break;
        case CellTag.Alien:
          next = _moon;
          _playerLocations[userId] = new MapInfo() { HasTreasure = false, Position = next };
          break;
        case CellTag.Moon:
          _playerLocations[userId] = new MapInfo() { HasTreasure = playerLocation.HasTreasure, Position = next };
          break;
        case CellTag.Earth:
          // win
          if (playerLocation.HasTreasure)
            _isWin[userId] = true;
          _playerLocations[userId] = new MapInfo() { HasTreasure = playerLocation.HasTreasure, Position = next };
          break;
        case CellTag.BlackHole:
          next = next.GetNextBlackHole();
          _playerLocations[userId] = new MapInfo() { HasTreasure = playerLocation.HasTreasure, Position = next };
          break;
        case CellTag.MeteorBelt:
          var curr = playerLocation.Position;

          if (curr.CellType == CellTag.MeteorBelt && (curr.IsNextMeteorBelt(next) || next.IsNextMeteorBelt(curr)))
            next = next.GetNextMeteorBelt();

          _playerLocations[userId] = new MapInfo() { HasTreasure = playerLocation.HasTreasure, Position = next };

          break;
      }

      return next;
    }

    #endregion
  }
}